#include "SpiCommunicator.h"

#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#include <cstring>
#include <netdb.h>
#include <arpa/inet.h>
#include <net/if.h>

#include "Log.h"
#include "ErrorCode.h"
#include "UdpCommunicator.h"
#include "INetwork.h"

constexpr int DEFAULT_TIMEOUT_SECONDS = 2;

UdpCommunicator::~UdpCommunicator() {
    close();
}


bool UdpCommunicator::allocHandle(void) noexcept {
    msocket = socket(AF_INET, SOCK_DGRAM, 0);
    bool result = true;
    if (0 > msocket) {
        std::string message{"connect failed"};
        LOG_ERROR(buildErrorMessage(msocket, errno, message).data());
        result = false;
    } else {
        int allowBroadcast{1};
        result = ::setsockopt(msocket, SOL_SOCKET, SO_BROADCAST, &allowBroadcast, sizeof allowBroadcast);
    }

    return result;
}



int  UdpCommunicator::bindToInterface(INetwork& pnetwork) {
    ifreq request;
    std::memset(&request, 0, sizeof (request));
    std::strncpy(request.ifr_name, pnetwork.mname.data(), sizeof(request.ifr_name));

    int result = ::setsockopt(msocket, SOL_SOCKET, SO_BINDTODEVICE, &request, sizeof (request));
    std::string message;
    if (0 > result) {
        std::string message{"binding failed"};
        LOG_ERROR(buildErrorMessage(result, errno, message).data());
    }
    LOG_DEDUG("binding socket [%d]->%s,%s", msocket, request.ifr_name, message.data());

    return result;
}




bool UdpCommunicator::setTimeout(INetwork& pnetwork) noexcept {
    timeval timeout{DEFAULT_TIMEOUT_SECONDS, 0}; //100 * 900

//    const int* pI = &DEFAULT_TIMEOUT_SECONDS;
    return setTimeout(timeout);
}


bool UdpCommunicator::setTimeout(const timeval& timeout) noexcept {
    int result = ::setsockopt(msocket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof (timeout));
    result = ::setsockopt(msocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));

    return !result;
}



bool  UdpCommunicator::connect(const IpAddressInfo& ipAddressInfo) noexcept {
    sockaddr_in& targetIpAddress = mtargetIpAddress;
    memset(&targetIpAddress, 0, sizeof targetIpAddress);

    char  buffer[MAX_BUFFER_SIZE];
    if (0 == targetIpAddress.sin_addr.s_addr) {
        targetIpAddress.sin_family = AF_INET;

        hostent hostinfo,*phost = nullptr;
        int     returnCode;
        int ret = gethostbyname_r(ipAddressInfo.ip.data(), &hostinfo, buffer, sizeof(buffer), &phost, &returnCode);
        if (ret || !phost) {
            LOG_ERROR("server address error:ret:[%d],phost:[%p],domain[%s]", ret, phost, ipAddressInfo.ip.data());

            return false;
        }
        //inet_pton(AF_INET, ipAddressInfo.ip.data(), &targetIpAddress.sin_addr);
        targetIpAddress.sin_addr = *((in_addr*)phost->h_addr_list[0]);
        targetIpAddress.sin_port = htons(ipAddressInfo.port);
//    std::memset(&targetIpAddress.sin_zero, 0, sizeof (targetIpAddress.sin_zero));
    }

    LOG_DEDUG("connecting to server:domain:[%s],ip:[%s]:%d", ipAddressInfo.ip.data(), inet_ntop(AF_INET, &targetIpAddress.sin_addr, buffer, sizeof(buffer)), ipAddressInfo.port);

    return allocHandle();
}



ssize_t UdpCommunicator::sendRaw(const void* data, size_t length) noexcept {
    ssize_t sent = ::sendto(msocket, data, length, 0, reinterpret_cast <const sockaddr*>(&mtargetIpAddress), sizeof mtargetIpAddress);

    std::string message;
    buildErrorMessage(sent, errno, message);
    StringUtility::dumpMemory(reinterpret_cast <const uint8_t*>(data), length, "%02x,", message);
    LOG_DEDUG("sent:%zd, data:%s", sent, message.data());

    return sent;
}


ssize_t UdpCommunicator::recvRaw(void* buffer, size_t length) noexcept {
    socklen_t len{0};
    return ::recvfrom(msocket, buffer, length, 0,  reinterpret_cast <sockaddr*>(msourceIpAddress), &len);
}



bool  UdpCommunicator::close(void) noexcept {

    return 0 == ::close(msocket);
}


